﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Threading;

using LTorrent.Extensions;

namespace LTorrent.Peers
{
    /// <summary>
    /// Расширенный класс пира - содержит информацию о клиенте, поддерживает проверку связи и передачу сообщений
    /// </summary>
    class ExtendedPeer:Peer
    {
        #region ExtendedPeer

        protected Int32 _keepAliveFlag = 0;
        protected Int32 _keepAliveMessagesCount = 0;

        public bool Peer_intresting //Пир заинтересован в нас
        {
            get;
            set;
        }
        public bool Peer_choking //пир блокирует нас (клиента)
        {
            get;
            set;
        }
        public bool Am_intresting //клиент заинтересован в пире
        {
            get;
            set;
        }
        public bool Am_choking //клиент блокирует пир
        {
            get;
            set;
        }
        public bool Seed
        {
            get;
            set;
        }
        //Поле битов.
        protected Byte[] _peerBitField;

        //информация по кускам данного сокета.
        protected MemoryStream _pieceData = null;
        protected UInt32 _currentPieceIndex;


        //современные BitTorrent-клиенты блокируют запрашивающего, если он "просит" больше, чем 16 Кб
        protected UInt32 _requestingSize = 32 * 1024;


#region This_params_must_be_sending_in_Caller_object_initialization
        //экземпляр объекта, для отправки сообщений.
        protected Caller _callbackInstance;
        public Caller CallBackInstance { set { _callbackInstance = value; } }

        //Ссылка на пиры. Инструмент для управления пирами. 
        //TODO: Исключить из финальной сборки.
        protected List<ExtendedPeer> _ourPeers;
        public List<ExtendedPeer> OurPeers
        {
            get { return _ourPeers; }
            set { _ourPeers = value; }
        }

        //длина куска
        protected UInt32 _pieceLength;
        public UInt32 PieceLength
        {
            get { return _pieceLength; }
            set
            {
                _pieceLength = value;
                if (_pieceLength < _requestingSize)
                    _requestingSize = _pieceLength;
            }
        }

        protected Mutex _access;
        public Mutex Access
        {
            set { _access = value; }
        }
#endregion
        

        //поток получения сообщений
        protected MemoryStream _receiveStream = null;

        public Byte[] ReceivedMessage
        {
            get
            {
                return _receiveStream.ToArray();
            }
        }

        protected Socket Connector = null;

        public bool TryToSetConnection(HandshakeMessage Message)
        {
            IPEndPoint IpE = new IPEndPoint(IPAddress.Parse(IP), Port);
            Connector = new Socket(IpE.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            AsyncCallback SendMessage = new AsyncCallback(PeerManager);

            Connector.BeginConnect(IpE,SendMessage,Message);

            return Connector.Connected;
        }

        protected void DecrementThreadsCount()
        {
            lock (_callbackInstance)
            {
                _callbackInstance.ThreadsCount--;
                _callbackInstance.CheckCompletionEvent();
            }
        }

        protected void PeerManager(IAsyncResult Result)
        {
            try
            {
                if (!Connector.Connected)
                {
                    throw new Exception("Can't get access to remote peer: " + IP);
                }

                //Connector.EndConnect(Result);

                Console.WriteLine("Connected to " + IP);

                //данный интерфейс будет заниматься отправкой сообщений.
                IPeerMessages PeerMessage = Result.AsyncState as IPeerMessages;
                if (PeerMessage == null)
                {
                    throw new Exception("There is not PerrMessage!");
                }

                if (!SendMessage(PeerMessage))
                {
                    throw new Exception("Can't send message correctly!");
                }

                if (!ReceiveMessage())
                {
                    throw new Exception("Can't receive message correctly!");
                }
                if (!GetHandShakeMessage())
                {
                    throw new Exception("Requested info_hash is not same peer's info_hash!");
                }
                Console.WriteLine("HandShake message Received");
                if (_receiveStream.Length > 68)//HandShake length
                {
                    Byte[] Temp = _receiveStream.ToArray().Skip(68).ToArray();
                    _receiveStream.Close();
                    _receiveStream = new MemoryStream();
                    _receiveStream.Write(Temp, 0, Temp.Length);
                } else 
                {
                    _receiveStream.Close();
                    _receiveStream = null;
                }

                //Хранит пересылаемые сообщения
                List<IPeerMessages> PeerMessages = new List<IPeerMessages>();

                //метка выхода
                Boolean isMustExit = false;

                Console.WriteLine("Start to work with " + IP);
                //TODO запустить и создать keep-alive таймер
                while (!_callbackInstance.IsLoaded && !isMustExit)
                {
                    if (_receiveStream == null)
                    {
                        //получаем сообщение
                        bool IsTakeMessage = false;
                        while (Connector.Available > 0)
                        {
                            _receiveStream = new MemoryStream();
                            ReceiveMessage(false);
                            IsTakeMessage = true;
                        }
                        if (!IsTakeMessage)
                        {
                            
                            if (PeerMessages.Count > 0)
                            {
                               
                                List<int> ItemsToDelete = new List<int>();
                                for (int i = 0; i < PeerMessages.Count; i++)
                                {
                                    if (PeerMessages[i] is RequestMessage)
                                    {
                                        if (Peer_choking || !Am_intresting)
                                        {
                                            _keepAliveFlag++;
                                            if (_keepAliveFlag == 100)
                                            {
                                                _keepAliveMessagesCount++;
                                                //PeerMessages.Add(new KeepAlive());
                                            }
                                            if (_keepAliveMessagesCount == 100)
                                            {
                                                throw new Exception("Пир блокирует клиента " + IP);
                                            }
                                            continue;
                                        }
                                    }
                                    Console.WriteLine("Send message to " + IP);
                                    SendMessage(PeerMessages[i]);
                                    Console.WriteLine("Sending complete to " + IP);
                                    ItemsToDelete.Add(i);
                                }
                                
                                for (int i = ItemsToDelete.Count - 1; i >= 0; i--)
                                {
                                    PeerMessages.RemoveAt(ItemsToDelete[i]);
                                }
                                
                            }
                            
                        }
                    }
                    else
                    {
                        //десериализируем сообщение
                        while (Connector.Available > 0)
                        {
                            ReceiveMessage(false);
                        }
                        ReceivedMessage PeerAnswer = new ReceivedMessage(_receiveStream.ToArray());
                        _receiveStream.Close();
                        _receiveStream = null;

                        switch (PeerAnswer.MessageID)
                        {
                            case MessageType.Choke:
                                {
                                    Peer_choking = true;
                                    break;
                                }
                            case MessageType.Unchoke:
                                {
                                    Peer_choking = false;
                                    break;
                                }
                            case MessageType.Intrested:
                                {
                                    Peer_intresting = true;
                                    break;
                                }
                            case MessageType.NotIntrested:
                                {
                                    Peer_intresting = false;
                                    break;
                                }
                            case MessageType.BitField:
                                {//Получаем карту кусков
                                    Console.WriteLine("Received BitFiled");
                                    _peerBitField = PeerAnswer.PieceMap;
                                    //проверяем заинтересованность
                                    UInt32? QueryResult = _callbackInstance.FindNewPieceIndex(_peerBitField);

                                    if (QueryResult.HasValue)
                                    {
                                        PeerMessages.Add(new IntrestedMessage());
                                        Am_intresting = true;
                                        PeerMessages.Add(new UnchokeMessage());
                                        Am_choking = false;

                                        PeerMessages.Add(new RequestMessage(QueryResult.Value, 0, _requestingSize));
                                    }
                                    else
                                    {
                                        //TODO: определить алгоритм! пока что заглушка. если мы не заинтересованы в пире - мы просто его закроем.
                                        isMustExit = true;
                                    }
                                    break;
                                }
                            case MessageType.Have:
                                {//Пир достал еще один кусок. Запишем себе.
                                    _peerBitField.addPiece(PeerAnswer.Index);
                                    break;
                                }
                            case MessageType.KeepAlive:
                                {
                                    //TODO: не разрывать соединение. (Обновить таймер?)
                                    break;
                                }
                            case MessageType.Request:
                                {
                                    //TODO: реализовать Request общение, добавить в очередь данных ответ.
                                    break;
                                }
                            case MessageType.Piece:
                                {
                                    if (_pieceData == null)
                                    {
                                        _pieceData = new MemoryStream();
                                        _currentPieceIndex = PeerAnswer.Index;
                                    }
                                    else
                                    {
                                        if (_currentPieceIndex != PeerAnswer.Index) { throw new ArgumentException("Received index is incorrect!"); }
                                    }

                                    _pieceData.Write(PeerAnswer.Block, 0/*(Int32)PeerAnswer.Offset_begin*/, (Int32)PeerAnswer.Block.Length);

                                    if (_pieceData.Length == PieceLength)
                                    {
                                        //Кусок готов
                                        _callbackInstance.WriteNewPieceToTempDirectory(_pieceData.ToArray(), PeerAnswer.Index);
                                        _pieceData.Close();
                                        _pieceData = null;

                                        lock (_callbackInstance)
                                        {//Блочим на запросы к управляющему класу
                                            _callbackInstance.PieceDownloadingComplete(PeerAnswer.Index);
                                            
                                            UInt32? QueryResult = _callbackInstance.FindNewPieceIndex(_peerBitField);
                                            
                                            if (!QueryResult.HasValue)//задач нет, "уходим" от потока
                                                PeerMessages.Add(new NotIntrestedMessage());
                                            else
                                            {   
                                                //Получили новую задачу от управляющего потока
                                                UInt32 QueryIndex = QueryResult.Value;
                                                PeerMessages.Add(new RequestMessage(QueryIndex, 0, _requestingSize));
                                                _callbackInstance.SetLock(QueryIndex);
                                            } 
                                        }
                                        
                                    }
                                    else
                                    {
                                        UInt32 RequestingSize = _requestingSize > _pieceLength - _pieceData.Length ? (UInt32)(_pieceLength - _pieceData.Length) : _requestingSize;
                                        PeerMessages.Add(new RequestMessage(_currentPieceIndex, (UInt32)(_pieceData.Length), RequestingSize));  
                                    }
                                    break;
                                }
                            case MessageType.CloseSocket:
                                {
                                    isMustExit = true;
                                    break;
                                }
                        }
                    }
                    

                }

                Console.WriteLine("Close Socket " + IP);
            }
            catch (SocketException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                Console.WriteLine(ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("From protocol: " + ex.Message);
            }

            finally
            {
                 Connector.Close();
                DecrementThreadsCount();
                
            }
           
        }

        /// <summary>
        /// Обрабатывает HandShake сообщение.
        /// </summary>
        /// <returns>Если info_hash совпадают, возарщает true</returns>
        protected Boolean GetHandShakeMessage()
        {
            if (_receiveStream.Length < HandshakeMessage.Length)
                throw new Exception("Not Handshake message was received!");
            IEnumerable<Byte> Info_HashComparer = _receiveStream.ToArray().Skip(28).Take(20);
            Boolean Result;
            lock (_callbackInstance)
            {
                Result = _callbackInstance.CompareInfo_hash(Info_HashComparer);
            }
            return Result;
        }

        /// <summary>
        /// Чтение сообщения  с сокета
        /// </summary>
        /// <returns>Удачность операции</returns>
        protected Boolean ReceiveMessage(bool NewInstance = true)
        {
            
            Byte[] receivedBytes = new Byte[256];
            Int32 bytesCount = 0;
           
            if (NewInstance)
             _receiveStream = new MemoryStream();
           
            do
            {
                bytesCount = Connector.Receive(receivedBytes, receivedBytes.Length, 0);
               
                _receiveStream.Write(receivedBytes, 0, bytesCount);

            } while (bytesCount == receivedBytes.Length);

            return _receiveStream.Length != 0;
           

        }

        /// <summary>
        /// Отправка сообщения
        /// </summary>
        /// <param name="PeerMessage">Сообщение</param>
        /// <returns>Было ли сообщение отправлено полностью</returns>
        protected Boolean SendMessage(IPeerMessages PeerMessage)
        {
            //TODO завести таймер
           int sendedBytes = Connector.Send(PeerMessage.Message);
           return sendedBytes == PeerMessage.Message.Length;
        }

       
        //Клиент может запрашивать блоки - 
        /* Am_intresting = 1 ;
         * Peer_choking = 0;
         * Клиент отдает блоки при  -
         * Am_choking = 0
         * Peer_intresting = 1
         */
        public ExtendedPeer(string id, string ip, string port, Caller CallbackInstance = null)
            : base(id, ip, port)
        {
            _callbackInstance = CallbackInstance;
            Am_choking = Peer_choking = true;
            Am_intresting = Peer_intresting = false; 
        }

        public ExtendedPeer(byte[] CompactPeer, Caller CallbackInstance = null)
            : base(CompactPeer)
        {
            _callbackInstance = CallbackInstance;
            Am_choking = Peer_choking = true;
            Am_intresting = Peer_intresting = false; 
        }
            
         

        public UInt32 LastVisited { get; set; }
        public UInt32 ConnectStart { get; set; }
        public UInt32 ConnectTime { get; set; }
        //Pieces     n
        public int NumCompletePieces { get; set; }
        #endregion
    }
}
